using System;
using System.IO;
using System.Runtime.Caching;
using System.Runtime.Serialization.Formatters.Binary;
using System.ServiceModel.Syndication;
using System.Xml;
using OpenWaves.Caching;

namespace OpenWaves.Syndication
{
    public static class FeedAggregationServiceFactory
    {
        public static IFeedAggregationService CreateWithAsyncMemoryCache()
        {
            return CreateWithAsyncMemoryCache(TimeSpan.FromHours(1));
        }

        public static IFeedAggregationService CreateWithAsyncMemoryCache(TimeSpan expiration)
        {
            var cache = new AsyncConcurrentCache<SyndicationFeed>(
                new MemoryCacheWrapper<AsyncCacheEntry<SyndicationFeed>>(new MemoryCache("Feeds")));

            cache.AsyncOperationFailed += (sender, args) =>
            {
                if (args.Exception is SyndicationFeedLoaderException)
                {
                    args.AbsoluteExpiration = Time.Now + TimeSpan.FromHours(1);
                    args.Exception = null;
                }
            };

            return new FeedAggregationService(new CachingSyndicationFeedLoader(cache, expiration));
        }

        public static IFeedAggregationService CreateWithAsyncFileSystemCache(string feedsCachePath)
        {
            var cache = new AsyncConcurrentCache<SyndicationFeed>(
                new MultilevelCache<AsyncCacheEntry<SyndicationFeed>>(
                    new MemoryCacheWrapper<MultilevelCacheEntry<AsyncCacheEntry<SyndicationFeed>>>(new MemoryCache("Feeds")),
                    new FileSystemCache<MultilevelCacheEntry<AsyncCacheEntry<SyndicationFeed>>>(feedsCachePath, SerializeCacheEntry, DeserializeCacheEntry)));

            cache.AsyncOperationFailed += (sender, args) =>
                                              {
                                                  if (args.Exception is SyndicationFeedLoaderException)
                                                  {
                                                      args.AbsoluteExpiration = Time.Now + TimeSpan.FromHours(1);
                                                      args.Exception = null;
                                                  }
                                              };

            return new FeedAggregationService(new CachingSyndicationFeedLoader(cache));
        }

        internal static MultilevelCacheEntry<AsyncCacheEntry<SyndicationFeed>> DeserializeCacheEntry(Stream stream)
        {
            var serializableEntry = (MultilevelCacheEntry<AsyncCacheEntry<string>>)new BinaryFormatter().Deserialize(stream);

            using (var xmlTextReader = new XmlTextReader(new StringReader(serializableEntry.Value.Value)))
            {
                var asyncCacheEntry = new AsyncCacheEntry<SyndicationFeed>(
                    SyndicationFeed.Load(xmlTextReader),
                    serializableEntry.Value.AbsoluteExpiration,
                    serializableEntry.Value.Exception);

                return new MultilevelCacheEntry<AsyncCacheEntry<SyndicationFeed>>(asyncCacheEntry, serializableEntry.AbsoluteExpiration);
            }
        }

        internal static void SerializeCacheEntry(MultilevelCacheEntry<AsyncCacheEntry<SyndicationFeed>> entry, Stream stream)
        {
            using (var stringWriter = new StringWriter())
            using (var writer = new XmlTextWriter(stringWriter))
            {
                entry.Value.Value.SaveAsAtom10(writer);
                writer.Flush();

                var serializableAsyncEntry = new AsyncCacheEntry<string>(
                    stringWriter.ToString(),
                    entry.Value.AbsoluteExpiration,
                    entry.Value.Exception);

                new BinaryFormatter().Serialize(stream, new MultilevelCacheEntry<AsyncCacheEntry<string>>(serializableAsyncEntry, entry.AbsoluteExpiration));
            }
        }
    }
}